/*******************************************************************************
 * Copyright 2007, 2009 InnovaSoft SA http://www.innovasoft.biz/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
 
package biz.innovasoft.nodb;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;

import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;

import biz.innovasoft.nodb.annotations.Query;
import biz.innovasoft.nodb.annotations.Transaction;
import biz.innovasoft.nodb.annotations.TransactionWithQuery;
import biz.innovasoft.nodb.impl.FindHelper;
import biz.innovasoft.nodb.impl.GenericRepository;
import biz.innovasoft.nodb.index.IndexManager;
import biz.innovasoft.nodb.query.QueryCriteria;
import biz.innovasoft.nodb.query.QueryCriteriaImpl;
import biz.innovasoft.nodb.query.QueryType;
import biz.innovasoft.nodb.transaction.AnnotatedQuery;
import biz.innovasoft.nodb.transaction.AnnotatedTransaction;
import biz.innovasoft.nodb.transaction.AnnotatedTransactionWithQuery;

public class RepositoryProxy implements InvocationHandler {

	protected Prevayler prevayler;
	protected GenericRepository prevSystem;
	protected Object proxy;
	private final RepositoryConfiguration config;
	
	private Timer snapshotTimer;
	
	public RepositoryProxy (RepositoryConfiguration config) {
		
		this.proxy = newInstance(Repository.class);
		this.config = config;
		
		try {
			
			PrevaylerFactory factory = new PrevaylerFactory ();
			factory.configureJournalFileAgeThreshold(config.getJournalFileAge());
			factory.configureJournalFileSizeThreshold(config.getJournalFileSize());
			factory.configurePrevalenceDirectory(config.getRepositoryPath());
			factory.configureTransactionFiltering(config.isPrevaylerTransactionFiltering());
			factory.configurePrevalentSystem(new GenericRepository(config.isCompressed()));
						
			this.prevayler = factory.create();
			this.prevSystem = (GenericRepository)prevayler.prevalentSystem();
			prevSystem.initialize(config);
			
			if (config.isReindexAtStartup()) {
				prevSystem.reindex();
			}
			
			if (config.isAutomaticSnapshots()) {
				snapshotTimer = new Timer(true);
				snapshotTimer.scheduleAtFixedRate(new SnapshotTask(prevayler), 
						TimeUnit.MINUTES.toMillis(config.getAutomaticSnapshotsTime()), 
						TimeUnit.MINUTES.toMillis(config.getAutomaticSnapshotsTime()));
			}

		} catch (Exception e) {
			throw new RepositoryException("Can't open Repository at location: " + config.getRepositoryPath() , e);
		}
	}

	public Object getProxy() {
		return proxy;
	}

	protected Object newInstance(Class<?> cls) {
		Object proxy = Proxy.newProxyInstance(cls.getClassLoader(),
				new Class[] { cls }, this);
		return proxy;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		
		/*
		 * Execute transactions and return values.
		 */
		if (method.isAnnotationPresent(TransactionWithQuery.class)) {
			try {
				Class<?> types[] = method.getParameterTypes();
				return prevayler.execute(new AnnotatedTransactionWithQuery(method
					.getName(), types, args));
			} catch (Exception e) {
				throw e.getCause();
			}
		} 
		/*
		 * Excecute transactions
		 */
		if (method.isAnnotationPresent(Transaction.class)) {
			try {
				Class<?> types[] = method.getParameterTypes();
				prevayler.execute(new AnnotatedTransaction(method.getName(), types,
						args));
			} catch (Exception e) {
				if ( e instanceof InvocationTargetException) {
					throw e.getCause();
				}
				throw e;
			}
			return null;
		}
		
		/*
		 * Execute queries against the repository.
		 */
		if (method.isAnnotationPresent(Query.class)) {
			try {
				if (config.getRepositoryType() == RepositoryType.MULTI_THREADED) {
					return executeNonPrevalentQuery (method, args);
				}
				Class<?> types[] = method.getParameterTypes();
				return prevayler.execute(new AnnotatedQuery(method.getName(),
						types, args));
				
			} catch (Exception e) {
				throw e.getCause();
			}
		}
			
		
		/*
		 * Create the criterias.
		 */
		if (method.getName().equals("createCriteria")) {
			if (args.length == 1) {
				return createCriteria ((Class<?>)args[0], false, QueryType.PREVALENT);
			} else if (args.length == 2 ) {
				if ( args[1] instanceof QueryType) {
					return createCriteria ((Class<?>)args[0], false, (QueryType)args[1]);
				}
				return createCriteria ((Class<?>)args[0], (Boolean)args[1], QueryType.PREVALENT);
			}
			return createCriteria ((Class<?>)args[0], (Boolean)args[1], (QueryType)args[2]);
		}
		
		/*
		 * Take Snapshot of the repository.
		 */
		if (method.getName().equals("takeSnapshot")) {
			takeSnapshot();
			return null;
		}
		return method.invoke(this.prevSystem, args);
	}
	
	private QueryCriteria createCriteria (Class<?> clazz, boolean isStricted, QueryType type) {
		IndexManager index = ((GenericRepository)prevSystem).getIndexManager(clazz);
		FindHelper finder;
		if (type.equals(QueryType.PREVALENT)) {
			finder = new FindHelper ((Repository)this.proxy, index, isStricted);
		} else {
			finder = new FindHelper ((GenericRepository)prevSystem, index, isStricted);
		}
		return new QueryCriteriaImpl (clazz, finder, isStricted);
	}
	
	private Object executeNonPrevalentQuery (Method method, Object[] args) throws Exception {
		Class<?> types[] = method.getParameterTypes();
		Class<?> copyTypes[] = getCopyTypes(types);
		Object[] copyArgs = getCopyArgs(args);
		return prevayler.execute(new AnnotatedQuery(method.getName(),
				copyTypes, copyArgs));
	}

	private Object[] getCopyArgs(Object[] args) {
		Object[] copyArgs = new Object[args.length + 1];
		for (int t = 0; t < args.length; t++) {
			copyArgs[t] = args[t];
		}
		copyArgs[args.length] = true;
		return copyArgs;
	}

	private Class<?>[] getCopyTypes(Class<?>[] types) {
		Class<?>[] copyTypes = new Class<?>[types.length + 1];
		for (int t = 0; t < types.length; t++) {
			copyTypes[t] = types[t];
		}
		copyTypes[types.length] = boolean.class;
		return copyTypes;
	}
	
	protected void close () throws RepositoryException {
		try {
			this.prevayler.close();
		} catch (IOException e) {
			throw new RepositoryException ("Error closing the repository");
		} finally {
			((GenericRepository)prevSystem).close();
		}
	}
	
	public void takeSnapshot() throws RepositoryException {
		try {
			prevayler.takeSnapshot();
		} catch (IOException e) {
			throw new RepositoryException(
					"Error taking snapshot. Repository base class:"
							+ (prevSystem), e);
		}
	}
	
	private static class SnapshotTask extends TimerTask {

		private Prevayler _prevayler;
		
		public SnapshotTask(Prevayler prevayler) {
			this._prevayler = prevayler;
		}
		
		public void run() {
			try {
				_prevayler.takeSnapshot();
			} catch (IOException e) {
				throw new RepositoryException(
						"Error taking snapshot.", e);
			}
			
		}
		
	}

}
